// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging.Loggers
{

	using System;
	using System.Collections;
	using System.Text;
	using System.Web.Mail;

	using NSpring.Logging.DataFormatters;
	using NSpring.Logging.EventFormatters;
	using NSpring.Logging.Exceptions;
	using System.Net.Mail;

	/// <summary>
	/// <para>
	/// A Logger implementation that sends events as email messages.  Each event received by the logger
	/// is run through a series of Filter objects; if a match is found, the associated EmailTemplate
	/// is used to construct and send an email message.  If no match is found, the logger's default template
	/// (see the DefaultEmailTemplate property) is used.
	/// </para>
	/// <para>
	/// This logger only accepts event formatters of type PatternEventFormatter.
	/// </para>
	/// <para>
	/// The method used for sending email is System.Web.Mail.SmtpServer.Send .
	/// </para>
	/// </summary>
	public class EmailLogger : Logger
	{
		private static Filter[] EMPTY_FILTER_ARRAY = { };

		private EmailTemplate defaultTemplate = null;

		private ArrayList filtersList = new ArrayList();
		private Filter[] filtersArray = EMPTY_FILTER_ARRAY;
		private Hashtable filterTemplateMap = new Hashtable();
		private Hashtable templateFilterMap = new Hashtable();

		private string serverAddress = null;

		/// <summary>
		/// Constructs a new instance.  The default template must be set before the logger can be opened
		/// </summary>
		public EmailLogger()
		{
			eventFormatter = new PatternEventFormatter("\n{msg}\n\n{data}\n");
			eventFormatter.DataFormatter = new TreeDataFormatter();
		}

		/// <summary>
		/// Constructs a new instance, using the supplied parameters to construct the default email template.  
		/// The address of the local SMTP server will be used to send mail
		/// </summary>
		/// <param name="_from">The hard-coded string or pattern to use for the EmailTemplate field "From"</param>
		/// <param name="_to">The hard-coded string or pattern to use for the EmailTemplate field "To"</param>
		/// <param name="_cc">The hard-coded string or pattern to use for the EmailTemplate field "CC"</param>
		/// <param name="_bcc">The hard-coded string or pattern to use for the EmailTemplate field "BCC"</param>
		/// <param name="_subject">The hard-coded string or pattern to use for the EmailTemplate field "Subject"</param>
		/// <param name="_body">The hard-coded string or pattern to use for the EmailTemplate field "Body"</param>
		public EmailLogger(string _from, string _to, string _cc, string _bcc, string _subject, string _body)
		{
			if ((_from == null) || (_to == null) || (_body == null))
			{
				throw new ArgumentNullException();
			}
			defaultTemplate = new EmailTemplate(_from, _to, _cc, _bcc, _subject, _body);
			defaultTemplate.BodyEncoding = Encoding.UTF8;
			defaultTemplate.IsBodyHtml = false;//MailFormat.Text;
			defaultTemplate.DataFormatter = new TreeDataFormatter();

			eventFormatter = new PatternEventFormatter(_body);
			eventFormatter.DataFormatter = new TreeDataFormatter();
		}

		/// <summary>
		/// Constructs a new instance, using the supplied parameters to construct the default email template
		/// </summary>
		/// <param name="_serverAddress">The address of the SMTP server to use</param>
		/// <param name="_from">The hard-coded string or pattern to use for the EmailTemplate field "From"</param>
		/// <param name="_to">The hard-coded string or pattern to use for the EmailTemplate field "To"</param>
		/// <param name="_cc">The hard-coded string or pattern to use for the EmailTemplate field "CC"</param>
		/// <param name="_bcc">The hard-coded string or pattern to use for the EmailTemplate field "BCC"</param>
		/// <param name="_subject">The hard-coded string or pattern to use for the EmailTemplate field "Subject"</param>
		/// <param name="_body">The hard-coded string or pattern to use for the EmailTemplate field "Body"</param>
		public EmailLogger(string _serverAddress, string _from, string _to, string _cc, string _bcc, string _subject, string _body)
		{
			if ((_serverAddress == null) || (_from == null) || (_to == null) || (_body == null))
				throw new ArgumentNullException();
			serverAddress = _serverAddress;
			defaultTemplate = new EmailTemplate(_from, _to, _cc, _bcc, _subject, _body);
			defaultTemplate.BodyEncoding = Encoding.UTF8;
			defaultTemplate.IsBodyHtml = false; // MailFormat.Text;
			defaultTemplate.DataFormatter = new TreeDataFormatter();

			eventFormatter = new PatternEventFormatter(_body);
			eventFormatter.DataFormatter = new TreeDataFormatter();
		}

		/// <summary>
		/// Constructs a new instance, using the supplied parameters to construct the default email template.  
		/// The subject pattern used is "[{levelName:case=upper}] {message:width=50}", the body pattern is "\n{message}\n{data}", 
		/// and the data formatter used is an instance of TreeDataFormatter.  
		/// The address of the local SMTP server will be used to send mail
		/// </summary>
		/// <param name="_from">The hard-coded string or pattern to use for the EmailTemplate field "From"</param>
		/// <param name="_to">The hard-coded string or pattern to use for the EmailTemplate field "To"</param>
		public EmailLogger(string _from, string _to)
		{
			if ((_from == null) || (_to == null))
			{
				throw new ArgumentNullException();
			}
			string subject = "[{ln:uc}] {msg:50w}";
			string body = "\n{msg}\n{data}";
			defaultTemplate = new EmailTemplate(_from, _to, null, null, subject, body);
			defaultTemplate.BodyEncoding = Encoding.UTF8;
			defaultTemplate.IsBodyHtml = false; // MailFormat.Text;
			defaultTemplate.DataFormatter = new TreeDataFormatter();

			eventFormatter = new PatternEventFormatter(body);
			eventFormatter.DataFormatter = new TreeDataFormatter();
		}


		/// <summary>
		/// Constructs a new instance, using the supplied parameters to construct the default email template.  
		/// The subject pattern used is "[{levelName:case=upper}] {message:width=50}", the body pattern is "\n{message}\n{data}", 
		/// and the data formatter used is an instance of TreeDataFormatter
		/// </summary>
		/// <param name="_serverAddress">The address of the SMTP server to use</param>
		/// <param name="_from">The hard-coded string or pattern to use for the EmailTemplate field "From"</param>
		/// <param name="_to">The hard-coded string or pattern to use for the EmailTemplate field "To"</param>
		public EmailLogger(string _serverAddress, string _from, string _to)
		{
			if ((_serverAddress == null) || (_from == null) || (_to == null))
			{
				throw new ArgumentNullException();
			}
			serverAddress = _serverAddress;
			string subject = "[{ln:uc}] {msg:50w}";
			string body = "\n{msg}\n{data}";
			defaultTemplate = new EmailTemplate(_from, _to, null, null, subject, body);
			defaultTemplate.BodyEncoding = Encoding.UTF8;
			defaultTemplate.IsBodyHtml = false; // MailFormat.Text;
			defaultTemplate.DataFormatter = new TreeDataFormatter();

			eventFormatter = new PatternEventFormatter(body);
			eventFormatter.DataFormatter = new TreeDataFormatter();
		}

		/// <summary>
		/// The email template that will be used to create MailMessage objects for all events
		/// not accepted by a filter
		/// </summary>
		public EmailTemplate DefaultEmailTemplate
		{
			get
			{
				return defaultTemplate;
			}
			set
			{
				lock (monitor)
				{
					if ((value == null) && (IsOpen))
					{
						throw new ArgumentNullException();
					}
					defaultTemplate = value;
					defaultTemplate.Body = ((PatternEventFormatter)eventFormatter).Pattern;
				}
			}
		}

		protected override bool UpdateEventFormatter(EventFormatter _eventFormatter)
		{
			if ((_eventFormatter == null) || (!(_eventFormatter is PatternEventFormatter)))
				return false;
			lock (monitor)
			{
				defaultTemplate.Body = ((PatternEventFormatter)_eventFormatter).Pattern;
			}
			return true;
		}


		/// <summary>
		/// The address of the SMTP server to use for sending email
		/// </summary>
		public string ServerAddress
		{
			get { return serverAddress; }
			set
			{
				lock (monitor)
				{
					if (IsOpen)
					{
						if (value == null)
							throw new ArgumentNullException();
						//SmtpMail.SmtpServer = value;
					}
					serverAddress = value;
				}
			}
		}

		/// <summary>
		/// Adds an email template to the logger.  Any events matching the specified filter
		/// will be formatted and sent using the specified template.  
		/// Filters are evaluated in the order they're added to the logger; only one 
		/// template may be added per filter, but multiple filters may point to the same
		/// template.
		/// </summary>
		/// <param name="_template"></param>
		/// <param name="_filter"></param>
		public void AddEmailTemplate(EmailTemplate _template, Filter _filter)
		{
			if ((_template == null) || (_filter == null))
				throw new ArgumentNullException();
			lock (monitor)
			{
				if (filterTemplateMap.ContainsKey(_filter))
					throw new ApplicationException("The specified filter is already mapped to a template");
				filtersList.Add(_filter);
				filterTemplateMap.Add(_filter, _template);
				ArrayList templateFilterList = (ArrayList)templateFilterMap[_template];
				if (templateFilterList == null)
				{
					templateFilterList = new ArrayList();
					templateFilterMap.Add(_template, templateFilterList);
				}
				templateFilterList.Add(_filter);
				filtersArray = new Filter[filtersList.Count];
				filtersList.CopyTo(filtersArray);
			}
		}

		/// <summary>
		/// Removes the specified template from the logger, together with any and
		/// all filters mapped to it
		/// </summary>
		/// <param name="_template"></param>
		public void RemoveEmailTemplate(EmailTemplate _template)
		{
			if (_template == null)
			{
				throw new ArgumentNullException();
			}
			lock (monitor)
			{
				ArrayList templateFilterList = (ArrayList)templateFilterMap[_template];
				if (templateFilterList == null)
					return;

				Filter filter;
				for (int x = 0; x < templateFilterList.Count; x++)
				{
					filter = (Filter)templateFilterList[x];
					filterTemplateMap.Remove(filter);
					filtersList.Remove(filter);
				}
				templateFilterMap.Remove(_template);
				filtersArray = new Filter[filtersList.Count];
				filtersList.CopyTo(filtersArray);
			}
		}

		/// <summary>
		/// Removes the filter from the logger.  If the template it points to is
		/// not pointed to by any other filters, it is also removed.
		/// </summary>
		/// <param name="_filter"></param>
		public void RemoveEmailTemplate(Filter _filter)
		{
			if (_filter == null)
			{
				throw new ArgumentNullException();
			}
			lock (monitor)
			{
				EmailTemplate template = (EmailTemplate)filterTemplateMap[_filter];
				if (template == null)
				{
					return;
				}
				filterTemplateMap.Remove(_filter);
				filtersList.Remove(_filter);
				ArrayList templateFilterList = (ArrayList)templateFilterMap[template];
				templateFilterList.Remove(_filter);
				if (templateFilterList.Count == 0)
				{
					templateFilterMap.Remove(template);
				}
				filtersArray = new Filter[filtersList.Count];
				filtersList.CopyTo(filtersArray);
			}
		}

		/// <summary>
		/// Removes all filters and templates from the logger, leaving the default
		/// filter to handle all events
		/// </summary>
		public void ClearEmailTemplates()
		{
			lock (monitor)
			{
				filtersList.Clear();
				filterTemplateMap.Clear();
				templateFilterMap.Clear();
				filtersArray = EMPTY_FILTER_ARRAY;
			}
		}

		/// <summary>
		/// Gets an array containing all filters currently mapped to templates
		/// in this logger
		/// </summary>
		public Filter[] EmailTemplateFilters
		{
			get
			{
				lock (monitor)
				{
					if (filtersArray.Length == 0)
					{
						return filtersArray;
					}
					Filter[] returnValue = new Filter[filtersArray.Length];
					Array.Copy(filtersArray, 0, returnValue, 0, returnValue.Length);
					return returnValue;
				}
			}
		}

		/// <summary>
		/// Gets the email template to which the specified filter is mapped
		/// </summary>
		/// <param name="_filter">The filter for which to return the template</param>
		/// <returns>The template to which the filter is mapped, or null if no mapping exists</returns>
		public EmailTemplate GetEmailTemplate(Filter _filter)
		{
			if (_filter == null)
				throw new ArgumentNullException();
			lock (monitor) { return (EmailTemplate)filterTemplateMap[_filter]; }
		}

		protected override void AcquireResources()
		{
			if (serverAddress == null)
				throw new ApplicationException("No server address set-- cannot start");
			else if (defaultTemplate == null)
				throw new ApplicationException("No default template set-- cannot start");
			//SmtpMail.SmtpServer = serverAddress;
		}

		/// <summary>
		/// Checks the list of filters; if a match is found, sends the event using the
		/// matching template.  If no match was found, sends the event using the default template
		/// </summary>
		/// <param name="_event">The event to format and send as an email message</param>
		protected override void Write(NSpring.Logging.Event _event)
		{
			Filter filter;
			EmailTemplate template = defaultTemplate;
			for (int x = 0; x < filtersArray.Length; x++)
			{
				filter = filtersArray[x];
				if (filter.Accepts(_event))
				{
					template = (EmailTemplate)filterTemplateMap[filter];
					break;
				}
			}
			//SmtpMail.Send(template.CreateMessage(_event, eventFormatter));
			SmtpClient smtpClient = new SmtpClient(ServerAddress);
			smtpClient.Send(template.CreateMessage(_event, eventFormatter));
		}
	}
}
